package com.bclz.nio.simpleReactor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 单Reactor单线程模型
 */
public class Reactor {


    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    private Reactor() {

    }

    public static Reactor build(int port) throws IOException {
        Reactor reactor = new Reactor();
        reactor.selector = Selector.open();
        reactor.serverSocketChannel = ServerSocketChannel.open();
        reactor.serverSocketChannel.bind(new InetSocketAddress("localhost", port));
        reactor.serverSocketChannel.configureBlocking(false);
        reactor.serverSocketChannel.register(reactor.selector, SelectionKey.OP_ACCEPT);
        return reactor;
    }

    public void run() {

        while (!Thread.interrupted()) {

            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    dispatch(selectionKey);
                    iterator.remove();
                }
            } catch (IOException e) {

                System.out.println(".....");
            }

        }

    }

    private void dispatch(SelectionKey selectionKey) {

        if (selectionKey.isAcceptable()) {
            register(selectionKey);
        } else if (selectionKey.isReadable()) {
            read(selectionKey);
        } else if (selectionKey.isWritable()) {//写操作的就绪条件为socket底层写缓冲区有空闲空间,绝大部分时间都是有空闲空间的，
            write(selectionKey);
        }

    }

    private void read(SelectionKey selectionKey) {
        System.out.println("READ...");
        try {
            StringBuilder sb = new StringBuilder();
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int read ;
            while ((read=socketChannel.read(byteBuffer)) > 0) {
                sb.append(new String(byteBuffer.array()));
                byteBuffer.clear();
            }
            if(read == -1){//-1代表连接关闭
                this.closeSocketChannel(selectionKey);
                return;
            }
            System.out.println("接受客户端消息: " + sb.toString());


            //注册写事件(写事件的意义在于，注册后，交给selector监听处理。而直接在这里进行write，会阻塞等待写完才会往下走)
//            socketChannel.write(ByteBuffer.wrap("already receive message...".getBytes()));
//            socketChannel.register(selector,selectionKey.interestOps()|SelectionKey.OP_WRITE,ByteBuffer.wrap("already receive message...".getBytes()));
            selectionKey.interestOps(selectionKey.interestOps()|SelectionKey.OP_WRITE);
            selectionKey.attach(ByteBuffer.wrap("already receive message...".getBytes()));

        } catch (Exception e) {

            System.out.println("客户端读取失败..."+e);
            this.closeSocketChannel(selectionKey);

        }
    }

    private void write(SelectionKey selectionKey) {
        try {
            System.out.println("WRITE...");
            ByteBuffer byteBuffer=(ByteBuffer)selectionKey.attachment();
            SocketChannel socketChannel=(SocketChannel)selectionKey.channel();
            int len=0;
            while (byteBuffer.hasRemaining()){
                //该方法只会写入小于socket's output buffer空闲区域的任何字节数
                //并返回写入的字节数，可能是0字节。
                len=socketChannel.write(byteBuffer);

                //当网络状况良好的情况下，不用作处理代码也能正常工作。
                // 现在一种极端情况，服务器端写事件就绪，
                // 向底层的写缓冲区写入一些数据后，服务器端到客户端的链路出现问题，服务器端没能把数据发送出去，此时底层的写缓冲区一直处于满的状态，
                // 假设writeBuffer中仍然还有没发送完的数据就会导致while循环空转，浪费CPU资源，同时也妨碍这个selector管理的其它通道的读写。
                if(len==0){ //说明socket底层缓冲区已满
                    break;
                }
            }
            //复制byteBuffer中未读取完的数据,将position位置放在保留数据之后,下次写数据就不会覆盖。作用:保留未读取的数据
            byteBuffer.compact();
            if(len>0){//证明已经读完
                byteBuffer.clear();
                //取消写事件，防止空转
                selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
                System.out.println("Already cancel write event...");
            }

        }catch (Exception e){

            System.out.println("WRITE ERROR...");
            closeSocketChannel(selectionKey);
        }

    }

    private void register(SelectionKey selectionKey) {
        try {

            SocketChannel socketChannel = this.serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(this.selector, SelectionKey.OP_READ);

        } catch (Exception e) {
            System.out.println("客户端注册失败...");
        }

    }

    private void closeSocketChannel(SelectionKey selectionKey){
        try {

            selectionKey.cancel();
            selectionKey.channel().close();
            System.out.println(selectionKey.hashCode()+" already close...");
        } catch (IOException ex) {
            System.out.println("close channel error...");
        }


    }

}
